﻿#if WINDOWS_UWP || NETFX_CORE
#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>$CreationDate$</CreationDate>
</File>
*/
#endregion

using System;
using System.ComponentModel;

using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;

using Outcoder.ComponentModel;
using Outcoder.Concurrency;

namespace Outcoder.NavigationModel
{
	public class NavigationMonitor
	{
		int attemptsToRetrieveService;

		public void Initialize()
		{
			var frame = GetFrame();

			if (frame == null)
			{
				if (++attemptsToRetrieveService < 5)
				{
					var context = Dependency.Resolve<ISynchronizationContext>();
					context.InvokeWithoutBlocking(Initialize);
				}
				else
				{
					throw new Exception("Exceeded attempts to retrieve frame.");
				}

				return;
			}

			frame.Navigating -= HandleNavigating;
			frame.Navigating += HandleNavigating;

			frame.Navigated -= HandleNavigated;
			frame.Navigated += HandleNavigated;
		}

		Frame GetFrame()
		{
			return (Frame)Window.Current?.Content;
		}

		void HandleNavigating(object sender, NavigatingCancelEventArgs e)
		{
			if (e.NavigationMode == NavigationMode.Back)
			{
				return;
			}

			ProcessDataContext(e,
				(navigationAware, eventArgs, parent) =>
				{
					if (eventArgs.Cancel)
					{
						return;
					}
					navigationAware.HandleNavigatingFrom(new NavigatingCancelArgs(eventArgs));
					if (eventArgs.Cancel && parent != null)
					{
						parent.ActivateViewModel((ViewModelBase)navigationAware);
					}
				});
		}

		void HandleNavigated(object sender, NavigationEventArgs e)
		{
			var frame = GetFrame();
			if (e.SourcePageType != frame.CurrentSourcePageType)
			{
				return;
			}
			ProcessDataContext(e,
				(navigationAware, eventArgs, _) => navigationAware.HandleNavigatedTo(
														new NavigationArgs(eventArgs)));
		}

		void ProcessDataContext<TEventArgs>(TEventArgs e,
			Action<INavigationAware, TEventArgs, ICompositeViewModel> notifyAction)
		{
			object dataContext = GetContentDataContext();
			if (dataContext == null)
			{
				return;
			}

			ProcessDataContext(dataContext, e, notifyAction, null);
		}

		static void ProcessDataContext<TEventArgs>(
			object dataContext, TEventArgs e, Action<INavigationAware, TEventArgs, ICompositeViewModel> notifyAction,
			ICompositeViewModel parent)
		{
			INavigationAware navigationAware = dataContext as INavigationAware;
			if (navigationAware != null)
			{
				notifyAction(navigationAware, e, parent);
			}

			/* Process child view models recursively. */
			var compositeViewModel = dataContext as ICompositeViewModel;
			if (compositeViewModel != null && compositeViewModel.ChildViewModels != null)
			{
				foreach (var childViewModel in compositeViewModel.ChildViewModels)
				{
					ProcessDataContext(childViewModel, e, notifyAction, compositeViewModel);
				}
			}
		}

		object GetContentDataContext()
		{
			var frame = GetFrame();
			FrameworkElement element = frame.Content as FrameworkElement;
			if (element != null)
			{
				return element.DataContext;
			}
			return null;
		}
	}
}
#endif